package ika.geo.osm;

import com.jhlabs.map.proj.TCEAProjection;
import ika.proj.Projection;
import ika.proj.Ellipsoid;
import ika.proj.MercatorProjection;
import java.awt.geom.Point2D;

/**
 * @author Bernhard Jenny, Institute of Cartography, ETH Zurich.
 */
public class Projector {

    private static final MercatorProjection mercator;

    static {
        mercator = new MercatorProjection();
        mercator.setMaxLatitude(OpenStreetMap.MAX_LAT);
        mercator.setMinLatitude(OpenStreetMap.MIN_LAT);
        mercator.setEllipsoid(Ellipsoid.SPHERE);
        mercator.initialize();
    }
    
    private Projection p = new TCEAProjection();;

    public Projector() {
        init(new double[0][0]);
    }

    /**
     *
     * @param pts Points in OSM Mercator projection.
     */
    public Projector(double[][] osmPoints) {
        init(osmPoints);
    }

    /**
     *
     * @param osmPoints Points in OSM Mercator projection
     */
    public void init(double[][] osmPoints) {

        // convert from OSM to geographical radian
        double[][] geoPts = new double[osmPoints.length][2];
        OSM2Geo(osmPoints, geoPts);

        // compute mean latitude and longitude of all points in radian
        Point2D lon0lat0 = meanPosition(geoPts);

        // center projection on mean longitude of all points
        p.setProjectionLongitude(lon0lat0.getX());
        //p.setProjectionLatitude(lon0lat0.getY());
        p.setTrueScaleLatitude(lon0lat0.getY());
        p.setEllipsoid(Ellipsoid.SPHERE);
        p.initialize();
    }

    public void setProjection (Projection newProjection) {
        newProjection.setEllipsoid(Ellipsoid.SPHERE);
        newProjection.setProjectionLongitude(p.getProjectionLongitude());
        newProjection.setTrueScaleLatitude(p.getTrueScaleLatitude());
        newProjection.initialize();
        p = newProjection;
    }

    public void setInitializedProjection (Projection newProjection) {
        p = newProjection;
    }

    public Projection getProjection() {
        return this.p;
    }
    
    private Point2D meanPosition(double[][] pts) {

        if (pts == null || pts.length == 0) {
            return new Point2D.Double(0, 0);
        }
        
        double lon0 = 0;
        double lat0 = 0;
        for (int i = 0; i < pts.length; i++) {
            lon0 += pts[i][0];
            lat0 += pts[i][1];
        }
        lon0 /= pts.length;
        lat0 /= pts.length;
        return new Point2D.Double(lon0, lat0);

    }

    public void intermediate2OSM(double[][] in, double[][] out) {

        // convert points to lon/lat
        Point2D.Double pt = new Point2D.Double();
        for (int i = 0; i < in.length; i++) {
            p.inverseTransformRadians(in[i][0], in[i][1], pt);
            out[i][0] = pt.x;
            out[i][1] = pt.y;
        }

        // inverse project from OSM to lon/lat in radians
        for (int i = 0; i < in.length; i++) {
            mercator.transform(Math.toDegrees(out[i][0]), Math.toDegrees(out[i][1]), pt);
            out[i][0] = pt.x;
            out[i][1] = pt.y;
        }

    }

    public void OSM2Intermediate(double[][] in, double[][] out) {

        // inverse project from OSM to lon/lat in radians
        Point2D.Double pt = new Point2D.Double();
        for (int i = 0; i < in.length; i++) {
            mercator.inverseTransformRadians(in[i][0], in[i][1], pt);
            out[i][0] = pt.x;
            out[i][1] = pt.y;
        }

        // convert points from lon/lat to the Transverse Cylindrical Equal-Area projection
        for (int i = 0; i < out.length; i++) {
            p.transform(Math.toDegrees(out[i][0]), Math.toDegrees(out[i][1]), pt);
            out[i][0] = pt.x;
            out[i][1] = pt.y;
        }

    }

    /**
     * converts lon/lat in degrees to OSM
     * @param in
     * @param out
     */
    public static void geo2OSM(double[][] in, double[][] out) {

        Point2D.Double pt = new Point2D.Double();

        for (int i = 0; i < in.length; i++) {
            mercator.transform(in[i][0], in[i][1], pt);
            out[i][0] = pt.x;
            out[i][1] = pt.y;
        }

    }

    /**
     * Transform from OSM Mercator to geographic coordinates in radians
     * @param in
     * @param out
     */
    public static void OSM2Geo(double[][] in, double[][] out) {

        // inverse project from OSM to lon/lat in radians
        Point2D.Double pt = new Point2D.Double();
        for (int i = 0; i < in.length; i++) {
            mercator.inverseTransformRadians(in[i][0], in[i][1], pt);
            out[i][0] = pt.x;
            out[i][1] = pt.y;
        }

    }

    public double[][] intermediate2geo(double[][] in) {

        // inverse project from OSM to lon/lat in degrees
        double[][] geographic = new double[in.length][2];
        Point2D.Double pt = new Point2D.Double();
        for (int i = 0; i < in.length; i++) {
            p.inverseTransformRadians(in[i][0], in[i][1], pt);
            geographic[i][0] = Math.toDegrees(pt.x);
            geographic[i][1] = Math.toDegrees(pt.y);
        }

        return geographic;
    }

    public void geo2Intermediate(double[][] in, double[][] out) {

        // inverse project from OSM to lon/lat in radians
        Point2D.Double pt = new Point2D.Double();

        // convert points from lon/lat to the Transverse Cylindrical Equal-Area projection
        for (int i = 0; i < in.length; i++) {
            p.transform(in[i][0], in[i][1], pt);
            out[i][0] = pt.x;
            out[i][1] = pt.y;
        }

    }
}
